package tld.test.interpreter.math
{
	import tld.test.interpreter.Context;
	import tld.test.interpreter.IContext;

	public class MathContext implements IContext
	{
		private var _parent:IContext;
		
		private const _names:Object =
		{
			"+" : add,
			"-" : subtract,
			"/" : divide,
			"*" : multiply,
			"%" : mod,
			"<<" : leftShift,
			">>" : rightShift,
			">>>" : rightUnsignedShift,
			"|" : bitOr,
			"&" : bitAnd,
			"sqrt" : Math.sqrt,
			"abs" : Math.abs,
			"min" : Math.min,
			"max" : Math.max,
			"random" : Math.random,
			"floor" : Math.floor,
			"ceil" : Math.ceil,
			"expt" : Math.pow,
			"round" : Math.round
		}
		
		public function MathContext(parent:IContext)
		{
			super();
			this._parent = parent;
		}
		
		// Math functions
		private function add(...rest):Number
		{
			var result:Number = 0;
			for each (var x:Number in rest) result += x;
			return result;
		}
		
		private function subtract(...rest):Number
		{
			var result:Number = rest.shift();
			if (!rest.length) result = -result;
			else for each (var x:Number in rest) result -= x;
			return result;
		}
		
		private function multiply(...rest):Number
		{
			var result:Number = 1;
			for each (var x:Number in rest) result *= x;
			return result;
		}
		
		private function divide(...rest):Number
		{
			var result:Number = rest.shift();
			for each (var x:Number in rest) result /= x;
			return result;
		}
		
		private function mod(...rest):Number
		{
			var result:Number = rest.shift();
			for each (var x:Number in rest) result %= x;
			return result;
		}
		
		private function leftShift(a:Number, b:Number):Number
		{
			return a << b;
		}
		
		private function rightShift(a:Number, b:Number):Number
		{
			return a >> b;
		}
		
		private function rightUnsignedShift(a:Number, b:Number):Number
		{
			return a >>> b;
		}
		
		private function bitOr(a:Number, b:Number):Number
		{
			return a | b;
		}
		
		private function bitAnd(a:Number, b:Number):Number
		{
			return a & b;
		}
		
		public function getParent():IContext { return this._parent; }
		
		public function getNames():Object
		{
			var result:Object;
			if (this._parent)
				result = Context.merge(
					this._names, this._parent.getNames());
			else result = this._names;
			return result;
		}
		
		public function setParent(parent:IContext):IContext
		{
			if (this._parent) parent.setParent(this._parent);
			return this._parent = parent;
		}
		
		public function put(name:String, value:Object = null):void
		{
			throw "cant add properties to MathContext";
		}
		
		public function resolve(name:String):Object
		{
			return this._names[name];
		}
	}
}